/* This client is for testing only
it has no packets' chain implemented */

#ifdef __cplusplus
extern "C" {
#endif

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <arpa/inet.h> /* for inet_addr */
#include <stdio.h>
#include <fcntl.h>
#include "console_client.h"

#define EMPTY_PACKET { PacketUnknown, 0, 0, 0, 2, finishPacket }

#ifndef amelog
#define amelog printf
#endif

static NetPackets NP[] = {
		{ PacketChatSystem, '0', '0', 2, 2, parseChat },
		{ PacketChatNotice, '0', '1', 2, 2, parseChat },
		{ PacketChat, '0', '2', 2, 2, parseChat },
		{ PacketChatShout, '0', '3', 2, 2, parseChat },
		{ PacketChatPM, '0', '4', 3, 2, parseChat },
		{ PacketChatTrade, '0', '5', 2, 2, parseChat },
		{ PacketChatParty, '0', '6', 2, 2, parseChat },
		{ PacketChatGuild, '0', '7', 2, 2, parseChat },
		EMPTY_PACKET, EMPTY_PACKET,
		{ PacketAuthRequest, '1', '0', 2, 2, finishPacket },
		{ PacketAuthSuccess, '1', '1', 2, 2, finishPacket },
		{ PacketAuthDeny, '1', '2', 0, 2, auth },
		EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET,
		{ PacketObjectLocated, '2', '0', 0, 8, objectLocated },
		{ PacketObjectMovement, '2', '1', 2, 4, finishPacket },
		{ PacketObjectTrajectoryChange, '2', '2', 2, 4, finishPacket },
		{ PacketObjectDisappeared, '2', '3', 2, 7+16, info },
		{ PacketLookAroundRequest, '2', '4', 0, 7+16, info },
		{ PacketMapQuery, '2', '5', 0, 3, mapQuery },
		EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET,
		{ PacketCharselectCountRequest, '3', '0', 0, 3, selectRequest },
		{ PacketCharInfoRequest, '3', '1', 1, 3, selectRequest },
		{ PacketCharselectRequest, '3', '2', 1, 2, selectRequest },
		{ PacketCharSelectedOK, '3', '3', 2, 2, finishPacket },
		{ PacketCharSelectedFailure, '3', '4', 2, 2, selectRequest },
		EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET, EMPTY_PACKET,
		{ PacketCharDisplacement, '4', '0', 2, 14, charDisplacement },
		{ PacketUnknown, '9', '9', 2, 2, finishPacket }
};

char ip[15];
int port;
struct sockaddr_in client;
int sockd, sockdd;
int packetnum, packetlen, rpacketlen, wmark, rmark, kmark, tmark;
char rbuff[1024], wbuff[1024], kbuff[1024], tmp[1024];

unsigned short chars_in_account;
//
struct timeval stimeout;

inline int client_init (const char* ip, int port, struct sockaddr_in *client, int *sockd, int *sockdd) {
	ssize_t sz;
	amelog("Connecting client to %s:%d\n", ip, port);
	if ((*sockd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		amelog("Couldn't set up the socket.\n");
		return -1;
	}

	memset (client, 0, sizeof(client));

	client->sin_family = AF_INET;
	client->sin_port   = htons (port);
 if (inet_aton(ip, &client->sin_addr) < 0) {
		amelog("Ip is not suitable.\n");
		return -1;
	}
	sz = sizeof(*client);

	if (connect(*sockd, (struct sockaddr *)client, sz)) {
		amelog("Couldn't connect, quitting.\n");
		return -1;
	}

	fcntl(*sockd, F_SETFL, O_NONBLOCK);
	fcntl(0, F_SETFL, O_NONBLOCK);
	fcntl(1, F_SETFL, O_NONBLOCK);
	fcntl(2, F_SETFL, O_NONBLOCK);

 	memset(rbuff, 0, sizeof (rbuff));
	memset(wbuff, 0, sizeof (wbuff));

	packetnum = PacketUnknown;

	return 0;
}

inline void do_send () {
	if (wmark > 0) {
		int size = write(sockd, wbuff, wmark);
		if (size < wmark) {
			memmove(wbuff, wbuff+size, wmark - size);
		}
		else {
			memset(wbuff,0,sizeof wbuff);
			wmark = 0;
		}
	}
}

inline void do_recv () {
	int size = read(sockd, rbuff+rmark, sizeof(rbuff) - rmark);
	if (size <= 0) {
		amelog("Disconnected from server.\n");
		exit(1);
	}
 	rmark += size;
	rbuff[rmark] = '\0';
}

inline void do_parse () {
	if (rmark >= 2 && packetnum == PacketUnknown) {
		rpacketlen = 2;
		packetnum = *(unsigned short *)(rbuff+0);
//		printf("Packet %d received\n", packetnum);
		if (packetnum >= 0 && packetnum < PacketUnknown) {
			rpacketlen = NP[packetnum].length;
		}
		else {
			rpacketlen = 2;
			packetnum = PacketUnknown;
		}
	}
	if (rmark >= rpacketlen) {
		int result;
		if (packetnum != PacketUnknown)
			result = NP[packetnum].proc(packetnum, rbuff);
		else
			result = 0;

		printf("Packet [%d] parsed\n", packetnum);

		if (result == 0) {
/*	Now that we have parsed-processed-used-whatever that packet it's time to
			get us rid of it. It is done by memmoving the new content on top of the old.
*/
			memmove(rbuff, rbuff+rpacketlen, rmark-rpacketlen);
			rmark -= rpacketlen;
		}
		packetnum = PacketUnknown;
	}
}

inline send_chat(int cmd, const char *argbuf) {
	unsigned short size;
	switch (cmd) {
		case 2:
		default:
/* Chat packet:
		 3 bytes header (2 bytes switch 1 length) and thecontent.
*/
		*((unsigned short *)(wbuff	+ 0)) = cmd;
		*((unsigned  char *)(wbuff + 2)) = 2 + 1 + sprintf(wbuff+3, "%s", argbuf);
		wmark += *(unsigned char *)(wbuff+2);
	}
	return 0;
}

inline send_command(int cmd, char *arg) {
	int n;
	char tmp[128];
	switch (cmd) {
		case 10:
			sscanf(arg, "%[a-z A-Z0-9]", tmp);
			*((unsigned short *)wbuff) = cmd;
			*((unsigned char *)wbuff+2) = (strlen(tmp) + 3);
			strcpy(wbuff+3, tmp);
			wmark += 3 + strlen(tmp);
			break;
		case PacketMapQuery :
			sscanf(arg, "%d", &n);
			*(unsigned short *)wbuff = PacketMapQuery;
			*(unsigned short *)(wbuff+2) = n;
			wmark += 4;
		case 32:
			sscanf(arg, "%d", &n);
	 	*((unsigned short *)(wbuff	+ 0)) = cmd;
	 	*((unsigned short *)(wbuff + 2)) = n;
			wmark += 4;
			break;
		case 31:
		case 30:
		default:
			amelog("Sending command %d\n", cmd);
		 	*((unsigned short *)(wbuff	+ 0)) = cmd;
				wmark += 2;
	}
}

inline void parsekb () {
	// Parsing keyboard buffer
	if (kbuff[0] == '@') {
		// Then it ish command
		int cmd;
		if (sscanf(kbuff, "@%d %[^\r\n]", &cmd, tmp) == 2) {
			amelog("Command %d with argument parsed.\n", cmd);
		}
		else if (sscanf(kbuff, "@%d", &cmd, tmp) == 1) {
			amelog("Command %d without arguments parsed.\n", cmd);
		}
		else {
			amelog("Nothing parsed.\n");
			return;
		}

		if (cmd > 1 && cmd < 8) {
			send_chat(cmd, tmp);
		}
		else {
			send_command(cmd, tmp);
		}
	}
	else {
		// Chatz
		kbuff[kmark] = '\0';
		amelog("\b\bme : %s\n", kbuff);
		send_chat(2, kbuff);
	}
}

inline void do_readkb () {
	char tmp[255];
	memset(tmp, 0, sizeof(tmp));
	int size = read(0, tmp, sizeof(tmp));
	if (size <= 0)
		return;

	char *c = tmp;
	for (;*c;c++) {
			if (*c == 8 && kmark > 0) {
				kmark--;
		 }
		 else if (*c >= 32) {
				*(kbuff+kmark) = *c;
				kmark++;
			}
	}

	kbuff[kmark] = '\0';
	if (kmark > 1)
		parsekb();

	memset(kbuff, 0, sizeof kbuff);
	kmark = 0;
}

int main (int argc, char *argv[]) {

	if (argc == 2) {
		port = strtol(argv[1], 0);
	}
	else if (argc == 3) {
		strncpy(ip, argv[1], sizeof ip);
		port = strtol(argv[2], 0);
	}
	else {
		port = 44443;
		strcpy(ip, "127.0.0.1");
	}

	if (client_init(ip, port, &client, &sockd, &sockdd))
		return -1;

	stimeout.tv_sec 	= 0;
	stimeout.tv_usec 	= 0;

	/*
	*(unsigned short *)(wbuff+0) = 10;
	*(unsigned char *)(wbuff+2) = 3 + sprintf(wbuff+3, "kompadre kompadre");
	wmark += *(wbuff+2);
	*/

	fd_set rports, wports;

	while (1) {
		FD_ZERO(&rports); FD_ZERO(&wports);

		FD_SET(1, &rports);
		FD_SET(sockd, &rports);
		FD_SET(sockd, &wports);

		select(10, &rports, &wports, 0, &stimeout);
		if (FD_ISSET(sockd, &wports))
			do_send();
		if (FD_ISSET(1, &rports))
			do_readkb();
		if (FD_ISSET(sockd, &rports))
			do_recv();
		if (rmark > 0) {
			do_parse();
		}
		fflush(stdin);
		usleep(1);
	}
}

int info(int packenum, void *p) {
	if (packetnum == 23)
		amelog("You can see a char called [%s]...\n", p+7);
	else if (packetnum == 24)
		amelog("You can see a mob [%s]...\n", p+7);
	return 0;
}

int finishPacket(int packetnum, void *p) {
	return 0;
}

int parseChat (int packetnum, void *p) {
	if (p == 0) return;
	rpacketlen =  *((char *)p+2);
	if (rpacketlen < 3 || rpacketlen > 255) {
		return 0;
	}

	int sender;

	if (packetnum == 0) {
		// Sender is system
		sender = 0;
	}
	else {
		sender = *(unsigned short *)(p+3);
	}

	if (rmark < rpacketlen)
		return 1;

	amelog("%d : %.*s\n", sender, (rpacketlen - (sender ? 5 : 3)), (rbuff+(sender ? 5 : 3))); // Yays
	return 0;
}

int auth(int packetnum, void *p) { return 0; }
int charSelect(int packetnum, void *p) { return 0; }

int selectRequest(int packetnum, void *p) {
	if (packetnum == 30) {
		amelog("Chars available %d\n", *(unsigned char *)(p+2) );
		return 0;
	}
	else if (packetnum == 31) {
		unsigned char *cp = p;
		rpacketlen = cp[2];
                if (rpacketlen <= 10 || rpacketlen > 255) {
                        // Discarding packet
                        amelog("Discarding packet by size %d\n");
                        packetlen = 10;
                        return 0;
                }
		else if (rpacketlen > rmark) {
			return 1;
		}

/*
		Packet looks like
		2b code 1b size (header)
		2b id, 1b sex 1b class 1b lvl 2b exp
		str name to size
		2 + 1 + 1 + 1 + 1 + 2 + n
*/

		unsigned id = *(unsigned short *)(p+3);
		unsigned sex = cp[5];
		unsigned class = cp[6];
		unsigned lvl = cp[7];
		unsigned exp = *(unsigned short *)(p+8);
		unsigned n = rpacketlen - 10;
		char *name = p+10;

		if (rmark < rpacketlen)
			return 1;

		amelog("Char received : %.*s (%d) %d %d %d\n", rpacketlen-10, p+10, id, sex, class, lvl, exp);
		return 0;
	}
	else { // packetnum == 32
		if (rmark >= 3)
			return 0;
	}
}

int charDisplacement(int packetnum, void *p) {
	printf("You're being displaced to %s %d:%d\n",
			 p+6, *(unsigned short *)(p+4), *(unsigned short *)(p+2));
	return 0;
}

int objectLocated(int packetnum, void *p) {
	printf ("You can see something [%d] on %d:%d\n",
		   *(unsigned short *)(p+2),
		   *(unsigned short *)(p+4),
		   *(unsigned short *)(p+6)
	);
	return 0;
}

int mapQuery (int packetnum, void *p) {
	int id = *(unsigned short *)(p+2);
	rpacketlen = *(unsigned short *)(p+4);
	if (rmark < rpacketlen)
		return 1;
	char type[16];
	char name[16];
	switch ( *(unsigned short *)(p+6) ) {
		case WO_CHAR :
			strcpy(type, "CHAR");
			break;
		case WO_MOB :
			strcpy(type, "MOB");
			break;
		case WO_AE :
			strcpy(type, "AE");
			break;
		case WO_ITEM:
			strcpy(type, "ITEM");
			break;
		case WO_NONE :
		default :
			strcpy(type, "UNKNOWN");
	}
	int subid =  *(unsigned short *)(p+6);
	memcpy(name, p+8, rpacketlen - 8);
	name[rpacketlen-8] = '\0';
	amelog("[%d] is a %s with name %s\n", subid, type, name);
}
